package design.pattern.Lu04TemplatePattern;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

import java.util.*;
import java.util.function.Function;

/**
 * @program JavaBase
 * @description: 模版模式 - 分布式事务
 * @author: zhanglu
 * @create: 2022-05-19 21:36:00
 */
@Slf4j
public class StrategyPattern01 {

    /******************数据模型定义******************/

    //接口定义: 请求时，什么接口的调用方式
    @Data
    @Builder
    @AllArgsConstructor
    @NoArgsConstructor
    static class ApiDefinition<T, R> {
        //服务名
        private String application;
        //服务根地址
        private String url;
        //接口名
        private String name;
        //接口地址
        private String api;
        //请求参数
        private T request;
        //响应参数
        private R response;
        //调度日志
        private StringBuffer executeMsg;
    }

    //响应结果
    @Data
    @Builder
    @AllArgsConstructor
    @NoArgsConstructor
    static class LuResponse<U> {
        private Integer code;
        private String message;
        private U data;
        private Map<String, Object> params = new LinkedHashMap<>();

        private boolean isSuccess() {
            return code > 0;
        }

        public LuResponse<U> params(String k, Object v) {
            this.params.put(k, v);
            return this;
        }
    }

    //T:接口请求参数 R:接口响应参数 S:db执行结果 U:方法返回结果

    //db资源
    @Data
    @Builder
    @AllArgsConstructor
    @NoArgsConstructor
    static class TransactionResource<T, R> {
        private ApiDefinition<T, R> apiDefinition;
        private ApiDefinition<T, R> undoApiDefinition;
        private Function<ApiDefinition<T, R>, R> redo;
        private Function<ApiDefinition<T, R>, R> undo;
        private boolean success;
    }

    /******************模版模式******************/

    //分布式管理者-协调各个资源，要么都提交，要不都回滚
    @Slf4j
    static abstract class Transaction2PCManage<T, R> {
        private Object request;
        private List<TransactionResource<T, R>> resourceList;

        public Transaction2PCManage() {
            resourceList = new LinkedList<>();
        }

        public Transaction2PCManage<T, R> setRequest(Object request) {
            this.request = request;
            return this;
        }

        public Object getRequest() {
            return this.request;
        }

        protected TransactionResource<T, R> append(TransactionResource<T, R> resource) {
            this.resourceList.add(resource);
            return resource;
        }

        //模版方法
        protected LuResponse execute() {
            Integer code = 1;
            //执行全部的事务
            for (TransactionResource<T, R> resource : resourceList) {
                log.info("----------------------");
                R res = null;
                try {
                    log.info(StrUtil.format("服务[{}]-接口[{}]-事务执行开始",
                            resource.getApiDefinition().getApplication(), resource.getApiDefinition().getName()));
                    log.info("{}-请求参数:{}", resource.getApiDefinition().getName(), this.getRequest());
                    resource.getApiDefinition().setRequest((T) this.getRequest());
                    res = resource.redo.apply(resource.getApiDefinition());
                    resource.getApiDefinition().setResponse(res);
                    resource.getApiDefinition().setExecuteMsg(new StringBuffer("redo成功"));
                    log.info("{}-响应结果:{}", resource.getApiDefinition().getName(), res);
                    this.setRequest(res);
                    resource.getApiDefinition().setResponse(res);
                    resource.setSuccess(true);
                    log.info(StrUtil.format("服务[{}]-接口[{}]-事务执行成功：{}",
                            resource.getApiDefinition().getApplication(), resource.getApiDefinition().getName(), res));
                } catch (Exception e) {
                    code = -1;
                    log.info(StrUtil.format("服务[{}]-接口[{}]-事务执行异常：{}",
                            resource.getApiDefinition().getApplication(), resource.getApiDefinition().getName(), e));
                    resourceList.stream().filter(o -> o.isSuccess()).forEach(undoResource -> {
                        log.info("----------------------");
                        if (undoResource.getUndoApiDefinition() == null) {
                            ApiDefinition<T, R> apiDefinition = undoResource.getApiDefinition();
                            ApiDefinition<T, R> undoApiDefinition = new ApiDefinition<>();
                            BeanUtil.copyProperties(apiDefinition, undoApiDefinition);
                            undoResource.setUndoApiDefinition(undoApiDefinition);
                        }
                        log.info(StrUtil.format("服务[{}]-接口[{}]-分布式事务-执行回滚开始：{}",
                                undoResource.getUndoApiDefinition().getApplication(), undoResource.getUndoApiDefinition().getName()));
                        log.info("{}-请求参数:{}", undoResource.getUndoApiDefinition().getName(), undoResource.getApiDefinition().getResponse());
                        this.setRequest(undoResource.getApiDefinition().getResponse());
                        R rs = undoResource.undo.apply(undoResource.getUndoApiDefinition());
                        undoResource.getUndoApiDefinition().setResponse(rs);
                        undoResource.getUndoApiDefinition().setExecuteMsg(new StringBuffer("undo成功"));
                        log.info("{}-响应结果:{}", undoResource.getUndoApiDefinition().getName(), rs);
                        log.info(StrUtil.format("服务[{}]-接口[{}]-分布式事务-执行回滚结束：{}",
                                undoResource.getUndoApiDefinition().getApplication(), undoResource.getUndoApiDefinition().getName(), rs));
                    });
                    break;
                }
            }
            return LuResponse.builder().code(code)
                    .data(resourceList)
                    .build();
        }

    }

    /******************控制器******************/

    @Slf4j
    static class OrderController extends Transaction2PCManage {

        public LuResponse createOrder(String data) {
            //实际上应该rpc调用，执行各个服务的redo、undo操作。这里采用本地伪代码的方式执行
           /*
                rpc ->  String res = HttpRequest.post(api.getUrl() + api.getApi()).body(request).execute().body();
            */
            this.setRequest(data).append(TransactionResource.builder()
                    .apiDefinition(ApiDefinition.builder()
                            .application("stock-service").url("http://127.0.0.1:8001/stock").api("/update")
                            .name("扣减库存")
                            .build())
                    .redo(api -> {
                        String request = this.getRequest().toString();
                        String sql = "update stock set num=num-1 where ...";
                        //db执行
                        Integer res = 1;
                        Integer id = 1;
                        Map<String, Object> m = new HashMap<>();
                        m.put("id", id);
                        m.put("num", 1);
                        return m;
                    })
                    .undo(api -> {
                        Map<String, Object> m = (Map<String, Object>) this.getRequest();
                        String sql = "update stock set num=num+1 where ...";
                        //db执行
                        Integer res = 1;
                        return res;
                    }).build());
            //生成订单
            this.append(TransactionResource.builder()
                    .apiDefinition(ApiDefinition.builder()
                            .application("order-service").url("http://127.0.0.1:8001/order").api("/save")
                            .name("保存订单")
                            .build())
                    .undoApiDefinition(ApiDefinition.builder()
                            .application("order-service").url("http://127.0.0.1:8001/order").api("/delete")
                            .name("删除订单")
                            .build())
                    .redo(api -> {
                        String request = this.getRequest().toString();
                        String sql = "insert into order('','') values('','')";
                        //db执行
                        Integer res = 1;
                        Integer id = 1;
                        return id;
                    })
                    .undo(api -> {
                        Integer id = (Integer) this.getRequest();
                        String sql = "delete from order where id=" + id;
                        //db执行
                        Integer res = 1;
                        return res;
                    }).build());
            this.append(TransactionResource.builder()
                    .apiDefinition(ApiDefinition.builder()
                            .application("account-service").url("http://127.0.0.1:8001/account").api("/update")
                            .name("扣减账户余额")
                            .build())
                    .redo(api -> {
                        String request = this.getRequest().toString();
                        String sql = "update account set gold=gold-10 where ...";
                        //db执行
                        Integer res = 1;
                        Integer id = 1;
                        Map<String, Object> m = new HashMap<>();
                        m.put("id", id);
                        m.put("gold", 10);
                        //模拟分布式事务异常情况
                        int i = 1 / 0;
                        return m;
                    })
                    .undo(api -> {
                        Map<String, Object> m = (Map<String, Object>) this.getRequest();
                        String sql = "update account set gold=gold+10 where ...";
                        //db执行
                        Integer res = 1;
                        return res;
                    }).build());
            LuResponse response = this.execute();
            log.info("本次任务全部执行完毕-执行结果:{}", JSON.toJSON(response).toString());
            return response;
        }
    }

    /******************测试******************/

    @Test
    public void test01() {
        OrderController orderController = new OrderController();
        orderController.createOrder("lu ~ 购买 猫粮 20 元 1个 address...");
    }

}
